---
sidebar_position: 2.3
description: 授权、鉴权中心微服务
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';




# 授权、鉴权中心微服务
## 功能设计
### JWT
保持用户状态的技术称为会话，用户不需要每一次都输入账号和密码来说明自己的身份。<br/>

一种解决方案是Session数据的持久化，写入数据库或者别的持久层，各种服务收到请求后都向持久层请求数据，这种方案的优点是架构清晰，缺点是工作量比较大，另外是持久层挂了就会单点失败<br/>

另一种方案是服务器索性不保存 session 数据了 ，所有数据都保存在客户端，每次请求都发送到服务器，JWT就是这种方案的一个代表。<br/>

JSON Web Token(JWT)是一个开放标准，它定义了一种紧凑、自包含的方式，用于作为JSON对象在各方之间安全的传输信息。<br/>

JWT的原理是服务器认证以后会生成一个JSON对象并发送给用户，以后用户和服务端通信的时候都需要将这个JSON对象带上，服务器完全靠这个JSON对象来认证用户的身份。为了防止用户去篡改数据，服务器在生成这个对象的时候会加上签名，
服务器不保存任何的 Session 数据，服务器变成了无状态的，从而比较容易实现扩展。
#### 那些场景下可以考虑使用JWT？
* 用户授权
当我们使用JWT去实现会话的时候，JWT 中一定会包含用户信息、用户对应的权限，所以我们当然可以用 JWT 去实现用户的授权。
* 信息交换
JWT 说白了就是保存的加密数据，只是说最好不要保存大量的数据信息，所以说系统之间少量的信息交换我们一样可以使用 JWT 去实现。
#### JWT 的组成部分
* JWT 由三个部分组成：Header、Payload、Signature，且用圆点连接   xxx.yyy.zzz。
* Header：由两部分（Token类型，加密算法名称）组成，并使用Base64编码。
```json
{
  "alg": "HS256",
  "typ": "JWT"
}
```
* PayLoad：KV形式的数据，即想传输的数据（授权的话就是Token信息），这部分同样是使用Base64转换之后的。
* Signature：为了得到签名部分，你必须有编码过的Header、编码过的Payload、一个密钥，签名算法是Header中指定的那个，然后对它们签名即可。

:::tip
Signature是对前两部分的签名，防止数据被篡改,一是要指定一个密钥，这个密钥必须服务器知道，不能泄露给用户，二是使用Header中指定的签名算法，使用如下公式产生一个签名：<br/>

HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)

最终将三部分用圆点连接起来，构成了JWT。

形成JWT之后就可以返回给客户端了，客户端在收到服务器返回的JWT以后，可以存储在Cookie里面，也可以存储在本地内存里面，以后客户端每次和服务器通信都需要带上JWT字符串，服务器反向解析拿到payload中保存的用户信息完成会话与鉴权，由此知道是哪一个用户在访问。
:::

## 授权、鉴权中心微服务功能逻辑架构
* 客户端的请求会最先到达SpringCloudGateway，由网关再和鉴权中心微服务通信。
* 授权：将用户信息生成JWT返回给客户端，由于JWT在每一次客户端和服务器的交互中都存在，所以说JWT中的用户数据不应该过多，而且不能把特别私密的信息，比如用户密码存储在里面。
* 注册：只要注册了账号之后才有授权、鉴权等等动作。
* 鉴权：通过一个工具类实现，这个工具类负责对客户端传来的JWT实现鉴权，鉴别用户身份。

![](./img/鉴权授权中心1.png)

:::tip 鉴权微服务设计
* JWT并不强依赖于某一个框架，所以有单独提取出来不依赖于任何一个框架的前提。
* 电商系统包含多个微服务，每一个微服务都需要鉴权的功能来获取用户信息。所以这个功能应该是某个地方单独提供的。
* 将鉴权功能剥离出来，提到一个独立的工具类里面去实现。
:::
## 搭建授权鉴权中心微服务
### 数据表及ORM过程


### 生成RSA256公钥和私钥对
使用JWT进行信息传输的时候，需要对JWT的payload部分做信息加密，加密使用的是RSA256算法，这个算法通常会使用私钥加密，公钥解密的过程。
```java title='使用java类生成密钥对'
package com.wkq.commerce.service;

import cn.hutool.core.codec.Base64;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;

/**
 * <h1>RSA 非对称加密算法：生成公钥和私钥</h1>
 */
@SpringBootTest
@RunWith(SpringRunner.class)
@Slf4j
public class RSATest {
    @Test
    public void generateKeyBytes() throws Exception {
        //java.security包下有很多加密算法
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
        keyPairGenerator.initialize(2048);
        //生成公钥和私钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        //获取公钥和私钥对象
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        log.info("private key:[{}]", Base64.encode(privateKey.getEncoded()));
        log.info("public key:[{}]", Base64.encode(publicKey.getEncoded()));
    }
}
```
将私钥以常量的形式保存在授权中心，新建constant包，在包下新建`AuthorityConstant.java`
```java
package com.wkq.commerce.constant;

/**
 * <h1>授权需要使用的一些常量信息</h1>
 */
public final class AuthorityConstant {
    private static final String publicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwaFHlkxeUq0lMoCvr0+koT4wcyTFdEOz0EJLqU5rxZuG2iUcUnxyPjKskQBSwedU6Hhhy4b7ZutxpuviNDzNhp/iHQGQDqz1usQNIcRp1CLVWU6zz/tpPLqkXfJXc4k8e2LNzTuIAPlnFzt8eebAyNtwK0nl2U0eKB41dHcuwdF+tQkYZc8m+T4lto/DurKGZ4AqhrImKLhY2VrEEQTw53+7KqH+ZlGge3S3MdQvdU89ov/vFd3UtkHjkd87BC2a28uOkZYRx7PYh5MbKsG+65XddHKFpO+dowL3VEUljKkhCtS38fCn9/CYzK70CCKXTSZIhUwQrirrJwxWux9vUwIDAQAB";
    /*RSA私钥，出来授权中心以外，不暴露给任何的客户端*/
    public static final String PRIVATE_KEY = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDBoUeWTF5SrSUygK+vT6ShPjBzJMV0Q7PQQkupTmvFm4baJRxSfHI+MqyRAFLB51ToeGHLhvtm63Gm6+I0PM2Gn+IdAZAOrPW6xA0hxGnUItVZTrPP+2k8uqRd8ldziTx7Ys3NO4gA+WcXO3x55sDI23ArSeXZTR4oHjV0dy7B0X61CRhlzyb5PiW2j8O6soZngCqGsiYouFjZWsQRBPDnf7sqof5mUaB7dLcx1C91Tz2i/+8V3dS2QeOR3zsELZrby46RlhHHs9iHkxsqwb7rld10coWk752jAvdURSWMqSEK1Lfx8Kf38JjMrvQIIpdNJkiFTBCuKusnDFa7H29TAgMBAAECggEAbmqF5BNQhlzuJUp05MfJ3ZkVD9QcNRyWYWAsvTNXCaySzbrz16XXj9hdx6IjjydXp+ld+y3SFvKUMNe6eodhvC9YRaoqd/PxfLtnsaXLo4jpmK/koemALX1RVbFoZR4RO1sweKRF/ig6vWjSpsELln5sDINrausrEz2Gxg9gvmy2JOhXBTMYOtJgAGg/MJvKE4acwJntI5X+suWDCj2G/oG3ygrjRmwNuF5sN8AbZb0/Wt/QxO4pz3MbN9L56IuhSCrmnM/ogKomjeUVIhWJCZ+lSBFdidv0OWaVdDuaSfRLTxeD2ARzetiXhnqCGvwdMFiud1Ne7aSzT3SrEyh+oQKBgQDtBAWU1v1SEAzXglckdymhCRBYjpnXVdKxlpsgTGS+zK3tdURv6CVQSdsV5sYp1Fetjq6yu++SIiIZe+arrmuCiHCEN3CfpqzVbPvww5TM9o/lLqLoUWozXdewjHLyVi/O0b8HL/PJnCNqvso2+SZJ7c8rnO0Ynwt9m4DMDucKRQKBgQDRI6OdHJnl6c4R7pF00KcZmS2XjqsGJez0HLc276rmlvACnq/pne88vJmuSxt7N77yTueVQoLHGi/h52cpoPYPiBKFPnRuexneainDaVJoP+A/S8QfdIAx3w6DnflIaO1oOsOiWTUY4+XS5nNhQfZM50mH0r1Y4mh/aLEzGlg4twKBgAEYhqoUgGFTv6tzZ9G8V03uS8wNBLsRI4M8rgTtNX4IVjmF1EYsLZf8b23ffkUNPUaJJVegNNBZJg02HG/ow4h79wE1fGjQjXYDJfsL/HEiJrIpkAlA7xvDsE6Zp6JlMZ1kLNzspNTmyucgcLDmX/3xgoayGAqIGbZ/9I18+CZ9AoGAf0x1I4WsO2DUGOAg03zJqXAuayjg+s4M7A2qi5rYaKEJGF8k7ks+uraFyKxY3uhwpqbWmWixl6Vd7eBfT9x0ycFmnIoGbtUpStNN9m7eRSxUP9LLXuYXvcNq3zxB5wxPLXlPaTtpcWwOkQ/KvTFcTTUe8biZnke5mYvNCDEJ8AMCgYEAtVO4ohx8ffJayJMcaKcJJ0XS9pSjMhYgKrCHwH1LzsyAY5ZeKXoBzHaSQfsHYBB2y91ASXyz9+of8WGEheADGW174aZGTlNZx5KrSu10f4WevzvBdv3+vpJm1ZfsZW+Y2J5+KkVno3Zy5yVl6qCfXTxy9k6IGHtEC+YHKkeA2x4=";
    /*默认的TOKEN过期时间*/
    public static final Integer DEFAULT_EXPIRE_DAY = 1;
}
```
因为要使用各个客户端都需要使用公钥解密鉴权，所以将公钥存储在一个公共的地方，commerce-comment 包下，让所有的客户端都可以访问到公钥。<br/>
创建constant包，constant下面创建 CommonConstant.java。

### 基于JWT+RSA256的授权


## 总结

<Tabs>
    <TabItem value="token" label="基于token的身份认证" default>
        <ul>
            <li>JWT 与 Session 的差异相同点是，它们都是存储用户信息；然而，Session是在服务器端的，而JWT是在客户端的。</li>
            <li>JWT方式将用户状态分散到了客户端中，可以明显的减轻服务端的内存压力。
                <img  src={require('./img/基于JWT的身份认证.png').default}
                      alt="基于JWT的身份认证"/>
            </li>
        </ul>
    </TabItem>
    <TabItem value="server" label="基于服务器的身份认证" >
        <ul>
            <li>最为传统的做法，客户端存储 Cookie（一般是Session id）,服务器存储 Session</li>
            <li>Session 是每次用户认证通过后，服务器需要创建一条记录保存用户信息，通常是在内存中，随着认证的用户越来越多，服务器在这里的开销就会越来越大。</li>
            <li>在不同的域名之间切换时，请求可能会被禁止；即跨域问题。
                <img  src={require('./img/基于服务器的身份认证.png').default}
                      alt="基于服务器的身份认证"/>
            </li>
        </ul>
    </TabItem>


</Tabs>

### 优缺点对比
* 解析方法：JWT使用算法直接解析得到用户信息；Session需要额外的数据映射，实现匹配。
* 管理方法：JWT只有过期时间的限制；Session数据保存在服务器端，可控性更强。
* 跨平台：JWT就是一段字符串，可以任意传播；Session跨平台需要统一的解析平台，较为繁琐。
* 时效性：JWT一旦生成，独立存在，很难做特殊控制；Session时效性完全由服务端的逻辑说了算。

:::tip
各自都有优缺点，都是登录、授权的解决方案。
:::